// npx babel-node proxy_demo01.js

let _ = '';
const {log} = console;
const getGlobal = require('../util/global');

let test01 = function() {
    var obj = new Proxy({}, {
        get: function(target, propKey, receiver){
            log(`getting ${propKey}!`);
            return Reflect.get(target, propKey, receiver);
        },
        set: function(target, propKey, value, receiver) {
            log(`setting ${propKey}!`);
            return Reflect.set(target, propKey, value, receiver);
        }
    });

    obj.count = 1;
    ++obj.count;
};

// test01();

let test02 = function(){
    let proxy = new Proxy({}, {
        get: function(target, propKey){
            return 35;
        }
    });

    _ = proxy.time;
    log(_)
    _ = proxy.name;
    log(_)
    _ = proxy.title;
    log(_)
};

// test02();

let test03 = function(){
    let target = {};
    let handler = {};
    let proxy = new Proxy(target, handler);
    proxy.a = 'b';
    _ = target.a;

    log(_)
};

// test03();

let test04 = function(){
    var proxy = new Proxy({}, {
        get: function(target, propKey) {
            return 35;
        }
    });

    let obj = Object.create(proxy);
    _ = obj.time
    log(_)
};

// test04();

let test05 = function(){
    let handler = {
        get: function(target, name) {
            if (name === 'prototype') {
                return Object.prototype;
            }
            return 'Hello, ' + name;
        },

        apply: function(target, thisBinding, args){
            return args[0];
        },

        construct(target, args) {
            return { value: args[1] };
        }
    };

    let fproxy = new Proxy(function(x, y){
        return x + y;
    }, handler);

    _ = fproxy(1, 2);
    log(_)

    _ = new fproxy(1, 2);
    log(_)

    _ = fproxy.prototype === Object.prototype;
    log(_)

    _ = fproxy.foo === 'Hello, foo';
    log(_)
};

// test05();

// 下面的例子使用get拦截，实现数组读取负数的索引。

let test06 = function() {
    let createArray = function(...elements) {
        let handler = {
            get(target, propKey, receiver) {
                let index = Number(propKey);
                if (index < 0) {
                    propKey = String(target.length + index);
                }
                return Reflect.get(target, propKey, receiver);
            }
        };

        let target = [];
        target.push(...elements);
        return new Proxy(target, handler);
    };

    let arr = createArray('a', 'b', 'c');
    _ = arr[-1];
    log(_)
};

// test06();

// 实现属性的链式操作

let test07 = function(){
    var pipe = function(value) {
        var funcStack = [];
        var oproxy = new Proxy({}, {
            get: function(pipeObject, fnName) {
                if (fnName === 'get') {
                    return funcStack.reduce(function(val, fn){
                        console.log(fn);
                        return fn(val);
                    }, value);
                }
                // let win = getGlobal();
                // funcStack.push(win[fnName]);
                funcStack.push(fnName);
                return oproxy;
            }
        });

        return oproxy;
    };

    let double = n => n * 2;
    let pow = n => n * n;
    let reverseInt = n => n.toString().split("").reverse().join("") | 0;

    _ = pipe(3).double.pow.reverseInt.get;
    log(_);
};

// 代码运行失败
// test07();

let test08 = function(){
    // 利用get拦截，实现一个生成各种 DOM 节点的通用函数dom。
    const dom = new Proxy({}, {
        get(target, property) {
            return function(attrs = {}, ...children){
                const el = document.createElement(property);
                for (let prop of Object.keys(attrs)) {
                    el.setAttribute(prop, attrs[prop]);
                }
                for (let child of children) {
                    if (typeof child === 'string') {
                        child = document.createTextNode(child);
                    }
                    el.appendChild(child);
                }
                return el;
            }
        }
    });

    const el = dom.div({}, 
        'Hello, my name is ',
        dom.a({href: '//example.com'}, 'Mark'),
        '. I like:',
        dom.ul({},
            dom.li({}, 'The web'),
            dom.li({}, 'Food'),
            dom.li({}, '...actually that\'s it')
        )
    );

    document.body.appendChild(el);
};


let test09 = function() {
    const proxy = new Proxy({}, {
        get: function(target, key, receiver){
            return receiver;
        }
    });

    _ = proxy.getReceiver === proxy
    log(_)
};

// test09();

let test10 = function(){
    const proxy = new Proxy({}, {
        get: function(target, key, receiver) {
            return receiver;
        }
    });

    const d = Object.create(proxy);
    _ = d.a === d
    log(_)
};

// 上面代码中，d对象本身没有a属性，所以读取d.a的时候，会去d的原型proxy对象找。这时，receiver就指向d，代表原始的读操作所在的那个对象。
// test10();

let test11 = function(){
    const target = Object.defineProperties({}, {
        foo: {
            value: 123,
            writable: false,
            configurable: false
        },
    });

    const handler = {
        get(target, propKey) {
            return 'abc';
        }
    };

    const proxy = new Proxy(target, handler);

    // 会报错
    // _ = proxy.foo
    // log(_)
};

// test11();

let test12 = function(){
    let validator = {
        set(obj, prop, value) {
            if (prop === 'age') {
                if (!Number.isInteger(value)) {
                    throw new TypeError('The age is not an integer');
                }
                if (value > 200) {
                    throw new RangeError('The age seems invalid');
                }
            }

            obj[prop] = value;
        }
    };

    let person = new Proxy({}, validator);

    person.age = 100;

    log(person.age);

    // person.age = 'young';
    // person.age = 300;
};

// test12();

// 属性名的第一个字符使用下划线开头，表示这些属性不应该被外部使用

let test13 = function(){
    let invariant = function(key, action){
        if (key[0] === '_') {
            throw new Error(`Invalid attempt to ${action} private "${key}" property`);
        }
    };
    const handler = {
        get(target, key) {
            invariant(key, 'get');
            return target[key];
        },
        set(target, key, value) {
            invariant(key, 'set');
            target[key] = value;
            return true;
        }
    };

    const target = {};
    const proxy = new Proxy(target, handler);

    // proxy._prop = 'c';
    // _ = proxy._prop;

};

// test13();

let test14 = function(){
    const handler = {
        set(obj, prop, value, receiver) {
            obj[prop] = receiver;
        }
    };
    const proxy = new Proxy({}, handler);
    proxy.foo = 'bar';
    _ = proxy.foo === proxy;
    log(_);
};

// test14();

let test15 = function(){
    const handler = {
        set(obj, prop, value, receiver) {
            obj[prop] = receiver;
        }
    };
    const proxy = new Proxy({}, handler);
    const myObj = {};
    Object.setPrototypeOf(myObj, proxy);

    myObj.foo = 'bar';
    _ = myObj.foo === myObj;
    log(_)
};

// test15();

// 注意，如果目标对象自身的某个属性，不可写且不可配置，那么set方法将不起作用。

let test16 = function(){
    const obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 'bar',
        writable: false,
    });

    const handler = {
        set(obj, prop, value, receiver) {
            obj[prop] = 'baz';
        }
    };

    const proxy = new Proxy(obj, handler);
    proxy.foo = 'bz';
    _ = proxy.foo;
    log(_);
};

// test16();

// apply方法拦截函数的调用、call和apply操作。

let test17 = function(){
    var target = function(){ return 'I am the target'; };
    var handler = {
        apply(){
            return 'I am the proxy';
        }
    };

    var p = new Proxy(target, handler);

    _ = p()
    log(_)
};

// test17();

let test18 = function(){
    let twice = {
        apply(target, ctx, args) {
            return Reflect.apply(...arguments) * 2;
        }
    };
    let sum = function(left, right){
        return left + right;
    };

    let proxy = new Proxy(sum, twice);

    _ = proxy(1, 2)
    log(_)

    _ = proxy.call(null, 5, 6)
    log(_)

    _ = proxy.apply(null, [7, 8]);
    log(_)

    _ = Reflect.apply(proxy, null, [9, 10]);
    log(_)
};

// test18();

let test19 = function(){
    let handler = {
        has(target, key) {
            if (key[0] === '_') {
                return false;
            }
            return key in target;
        }
    };

    var target = { _prop: 'foo', prop: 'foo' };
    var proxy = new Proxy(target, handler);

    _ = '_prop' in proxy;
    log(_);
};

// test19();

let test20 = function(){
    let obj = { a: 10 };
    Object.preventExtensions(obj);

    let p = new Proxy(obj, {
        has(target, prop) {
            return false;
        }
    });

    // 会报错
    _ = 'a' in p;
};

// test20();

// 只对 `in` 有效, 对 `for...in` 无效

let test21 = function(){
    let stu1 = {name: '张三', score: 59};
    let stu2 = {name: '李四', score: 99};

    let handler = {
        has(target, prop) {
            if (prop === 'score' && target[prop] < 60) {
                log(`${target.name} 不及格`);
                return false;
            }
            return prop in target;
        }
    };

    let oproxy1 = new Proxy(stu1, handler);
    let oproxy2 = new Proxy(stu2, handler);

    _ = 'score' in oproxy1;
    log(_)

    _ = 'score' in oproxy2;
    log(_)

    for(let a in oproxy1) {
        log(oproxy1[a]);
    }

    for(let b in oproxy2) {
        log(oproxy2[b]);
    }
};

// test21();

let test22 = function(){
    let p = new Proxy(function(){}, {
        construct(target, args){
            log('called: ' + args.join(', '));
            return { value: args[0] * 10 };
        }
    });

    _ = (new p(1)).value;
    log(_)
};

// test22();

// construct方法返回的必须是一个对象，否则会报错。

let test23 = function(){
    let p = new Proxy(function(){}, {
        construct(target, argumentsList) {
            return 1;
        }
    });

    // 报错
    // TypeError: 'construct' on proxy: trap returned non-object ('1')
    _ = new p();
};

// test23();

let test24 = function(){
    let invariant = function(key, action){
        if (key[0] === '_') {
            throw new Error(`Invalid attempt to ${action} private "${key}" property`);
        }
    };
    let handler = {
        deleteProperty(target, key) {
            invariant(key, 'delete');
            delete target[key];
            return true;
        }
    };

    let target = { _prop: 'foo' };
    let proxy = new Proxy(target, handler);

    delete proxy._prop;
};

// test24();

let test25 = function(){
    let handler = {
        defineProperty(target, key, descriptor){
            return false;
        }
    };

    let target = {};
    let proxy = new Proxy(target, handler);
    proxy.foo = 'bar';

    log(proxy.foo);
};

// test25();

let test26 = function(){
    let handler = {
        getOwnPropertyDescriptor(target, key) {
            if (key[0] === '_') {
                return;
            }
            return Object.getOwnPropertyDescriptor(target, key);
        }
    };

    let target = { _foo: 'bar', baz: 'tar' };
    let proxy = new Proxy(target, handler);

    _ = Object.getOwnPropertyDescriptor(proxy, 'wat');
    log(_)

    _ = Object.getOwnPropertyDescriptor(proxy, '_foo');
    log(_)

    _ = Object.getOwnPropertyDescriptor(proxy, 'baz');
    log(_)
};

// test26();

let test27 = function(){
    let proto = {};
    let p = new Proxy({}, {
        getPrototypeOf(target) {
            return proto;
        }
    });

    _ = Object.getPrototypeOf(p) === proto;
    log(_);
};

// test27();


let test28 = function(){
    let p = new Proxy({}, {
        isExtensible(target) {
            log('called');
            return true;
        }
    });

    _ = Object.isExtensible(p);
    log(_)
};

// test28();

let test29 = function(){
    let p = new Proxy({}, {
        isExtensible(target) {
            return false;
        }
    });

    _ = Object.isExtensible(p);
    log(_)
};

// test29();

let test30 = function(){
    let target = {
        _bar: 'foo',
        _prop: 'bar',
        prop: 'baz'
    };

    let handler = {
        ownKeys(target) {
            return Reflect.ownKeys(target).filter(key => key[0] !== '_');
        }
    };

    let proxy = new Proxy(target, handler);
    for(let key of Object.keys(proxy)) {
        log(target[key]);
    }
};

// test30();

let test31 = function(){
    let proxy = new Proxy({}, {
        preventExtensions(target){
            log('called');
            Object.preventExtensions(target);
            return true;
        }
    });

    _ = Object.preventExtensions(proxy);
    log(_);
};

// test31();

let test32 = function(){
    let handler = {
        setPrototypeOf(target, proto) {
            throw new Error('Changing the prototype is forbidden');
        }
    };

    let proto = {};
    let target = function(){};
    let proxy = new Proxy(target, handler);

    Object.setPrototypeOf(proxy, proto);
};

// test32();

let test33 = function(){
    let target = {};
    let handler = {};

    let {proxy, revoke} = Proxy.revocable(target, handler);

    proxy.foo = 123
    _ = proxy.foo;
    log(_)

    revoke();
    _ = proxy.foo;
    
};

// test33();

let test34 = function(){
    const target = {
        m() {
            log(this === proxy);
        }
    };
    const handler = {};

    const proxy = new Proxy(target, handler);

    _ = target.m()

    _ = proxy.m();
};

// test34();

let test35 = function(){
    const _name = new WeakMap();

    class Person {
        constructor(name) {
            _name.set(this, name);
        }
        get name() {
            return _name.get(this);
        }
    }

    const jane = new Person('Jane');
    _ = jane.name;
    log(_);

    const proxy = new Proxy(jane, {});
    _ = proxy.name;
    log(_);
    log(jane.name);
};

// test35();

let test36 = function(){
    const target = new Date();
    const handler = {};
    const proxy = new Proxy(target, handler);

    _ = proxy.getDate();
};
// test36();

let test37 = function(){
    const target = new Date('2015-01-10');
    const handler = {
        get(target, prop) {
            if (prop === 'getDate') {
                return target.getDate.bind(target);
            }
            return Reflect.get(target, prop);
        }
    };
    const proxy = new Proxy(target, handler);

    _ = proxy.getDate();
    log(_);
};
// test37();

// Proxy 对象可以拦截目标对象的任意属性，这使得它很合适用来写 Web 服务的客户端。

let test38 = function(){
    let createWebService = function(baseUrl) {
        return new Proxy({}, {
            get(target, propKey, receiver) {
                return () => httpGet(baseUrl + '/' + propKey);
            }
        });
    };

    const service = createWebService('http://example.com/data');

    service.employees().then(json => {
        const employees = JSON.parse(json);
        // ...
    });
};




